classdef IOManager < handle
    % IOManager for reading input files
    % 核心：创建全局ID矩阵，创建全局节点坐标列表，创建实体对象数组，创建材料对象数组，创建荷载类对象

    properties
        ID       
        NodesTable
        EntityInstance
        MaterialInstance
        BC
        LoadSet
    end
    
    methods
        function obj = IOManager(filename)
            obj.NodesTable = obj.readNode(filename);
            obj.EntityInstance = obj.readEntity(filename);
            obj.MaterialInstance = obj.readMaterial(filename);
            obj.BC = obj.readDisplacementBoundary(filename);
            obj.LoadSet = obj.readLoads(filename);

            obj.ID = obj.generateGlobalID(obj.NodesTable, obj.EntityInstance, obj.BC);
        end

    end

    methods (Static)
        function NodesTable = readNode(filename)
            % === 读取节点, 返回全局节点数组 ===
            fid = fopen(filename, 'r');
            if fid < 0
                error('Cannot open file: %s', filename);
            end

            line = '';  
            while ~contains(line, '$Nodes')
                line = fgetl(fid);
                if ~ischar(line)
                    fclose(fid);
                    error('Unexpected end of file while searching for $Nodes');
                end
            end
            nNodes = str2double(fgetl(fid));
            NodesTable = zeros(nNodes, 3);
            for i = 1:nNodes
                data = sscanf(fgetl(fid), '%f');
                NodesTable(i,:) = data(2:4)';
            end

            fclose(fid);
        end

        function ID = generateGlobalID(Nodes, Entities, BC)
            % 生成全局ID矩阵（正确版本：在分配时跳过约束）
            % Nodes: [nNodes x dim] 全局节点坐标表
            % Entities: Entity对象数组
            % BC: 位移边界条件结构

            nNodes = size(Nodes,1);

            % --- 第一阶段：扫描以确定所需的最大自由度数 ---
            maxDofInModel = 0;
            for e = 1:length(Entities)
                entity = Entities(e);
                dofPerNode = entity.GetEntityNodeDof();
                if dofPerNode > maxDofInModel
                    maxDofInModel = dofPerNode;
                end
            end

            if maxDofInModel == 0
                ID = [];
                return;
            end

            % --- 新增：预处理边界条件，创建一个“约束标记矩阵” ---
            % isConstrained(n, d) = true 表示节点n的第d个自由度被约束
            isConstrained = false(nNodes, maxDofInModel);
            if nargin >= 3 && ~isempty(BC) && ~isempty(BC.FixedNodes)
                for k = 1:size(BC.FixedDofs, 1)
                    nodeID = BC.FixedNodes(k);
                    startDOF = BC.FixedDofs(k, 1);
                    endDOF   = BC.FixedDofs(k, 2);
                    % 确保不超出ID矩阵的列数
                    endDOF = min(endDOF, maxDofInModel);
                    if startDOF <= maxDofInModel
                        isConstrained(nodeID, startDOF:endDOF) = true;
                    end
                end
            end

            % --- 第二阶段：创建ID矩阵并分配ID（跳过约束） ---
            ID = zeros(nNodes, maxDofInModel);
            counter = 1;

            % 遍历所有实体，为活动自由度分配ID
            for e = 1:length(Entities)
                entity = Entities(e);
                dofPerNode = entity.GetEntityNodeDof();
                nodes = entity.getAllNodes();

                for n = nodes'
                    % 只为该实体实际拥有的自由度分配ID
                    for d = 1:dofPerNode
                        % --- 在分配前检查是否被约束 ---
                        % 如果该节点的自由度尚未分配，且未被约束
                        if ID(n, d) == 0 && ~isConstrained(n, d)
                            ID(n, d) = counter;
                            counter = counter + 1;
                        end
                    end
                end
            end
        end

        % 读取文件并返回Entity对象数组
        function EntityInstance = readEntity(filename)
            fid = fopen(filename, 'r');
            if fid < 0
                error('Cannot open file: %s', filename);
            end
            
            % === 读取物理组信息 ===
            line = '';
            while ~contains(line, '$PhysicalNames')
                line = fgetl(fid);
                if ~ischar(line)
                    fseek(fid, 0, 'bof');
                    break;
                end
            end

            EntityInstance = Entity.empty;

            if contains(line, '$PhysicalNames')
                nPhysicalNames = str2double(fgetl(fid));

                for i = 1:nPhysicalNames
                    dataLine = fgetl(fid);
                    data = strsplit(strtrim(dataLine));
                    if length(data) >= 3
                        dof = str2double(data{1});
                        entityID = str2double(data{2});
                        name = strrep(data{3}, '"', '');
                        
                        EntityInstance(i) = Entity(entityID, name, dof);
                    end
                end
                line = fgetl(fid);
                if ~contains(line, '$EndPhysicalNames')
                    error('Unexpected format in $PhysicalNames section');
                end
            end

            % === 读取单元 ===
            while ~contains(line, '$Elements')
                line = fgetl(fid);
                if ~ischar(line)
                    fclose(fid);
                    error('Unexpected end of file while searching for $Elements');
                end
            end
            totalElements = str2double(fgetl(fid));

            for ie = 1:totalElements

                ln = fgetl(fid);

                % ---检查是否到达文件末尾 ---
                if ln == -1
                    fprintf('Warning: Reached end of file prematurely at element %d.\n', ie);
                    break; % 退出 for 循环
                end

                % 清理和处理字符串
                ln = strtrim(ln);
                if isempty(ln)
                    continue; % 跳过空行
                end

                % 3. 转换为数字数组
                % 使用更稳健的方式，防止非数字行导致错误
                data = sscanf(ln, '%d');
                if isempty(data) || numel(data) < 3
                    % fprintf('Skipping malformed line: %s\n', ln); % 可以取消注释来调试
                    continue; % 跳过格式不正确的行
                end

                if isempty(ln), continue; end
                data = sscanf(ln, '%d')';
                if numel(data) < 3, continue; end
                
                gmshElementID = data(1); % 全局编号
                elementType = data(2);   % 单元编号，整型
                nTags = data(3);
                
                entityID = 0;
                materialIndex = 0;
                if nTags >= 2
                    entityID = data(4);
                    materialIndex = data(5);
                elseif nTags >= 1
                    entityID = data(4);
                end
                
                % 这里默认实体编号从 1 开始连续
                if entityID <= nPhysicalNames
                    firstNodeIdx = 4 + nTags;
                    if firstNodeIdx > numel(data), continue; end

                    nodeIDs = data(firstNodeIdx:end);

                    if ~isnumeric(nodeIDs)
                        error('Critical: nodeIDs is not numeric! Line was: %s', ln);
                    end

                    % 添加单元到mesh
                    EntityInstance(entityID).addElement(elementType, nodeIDs, materialIndex, gmshElementID);
                else
                    error(" 此单元实体编号并不存在!")
                end
            end
            
            fclose(fid);
        end

        % 从文件读取材料数据并创建材料对象数组
        function MaterialsInstance = readMaterial(filename)
            % 输入: filename - 文件名
            % 输出: materials - Material对象数组

            MaterialsInstance = Material.empty; % 初始化空数组

            fid = fopen(filename, 'r');
            if fid == -1
                error('无法打开文件: %s', filename);
            end

            % 查找材料数据块开始标记
            while ~feof(fid)
                line = fgetl(fid);
                if strcmp(strtrim(line), '$Materials')
                    break;
                end
            end

            if feof(fid)
                fclose(fid);
                error('未找到 $Materials 标记');
            end

            % 读取材料数量
            num_materials = str2double(fgetl(fid));

            % 读取每个材料数据
            for i = 1:num_materials
                line = fgetl(fid);
                if isempty(line) || strcmp(strtrim(line), '$EndMaterials')
                    break;
                end

                data = sscanf(line, '%f'); % 读取所有数字

                material_id = data(2);

                params = data(3:end); % 剩余参数
                param_cell = num2cell(params);
                % 直接传入参数数组
                MaterialsInstance(i) = Material(material_id, param_cell{:}); % 涉及到元胞数组的长度问题
            end

            % 查找结束标记（可选）
            while ~feof(fid)
                line = fgetl(fid);
                if strcmp(strtrim(line), '$EndMaterials')
                    break;
                end
            end

            fclose(fid);
        end

        function BC = readDisplacementBoundary(filename)
            % 从输入文件读取位移约束（起始-结束自由度）
            fid = fopen(filename,'r');
            if fid==-1
                error('无法打开文件 %s', filename);
            end

            BC.FixedNodes = [];
            BC.FixedDofs = [];
            BC.Values = [];  % 可留作0占位或后续扩展

            while ~feof(fid)
                line = strtrim(fgetl(fid));
                if startsWith(line,'$DisplacementBoundary')
                    data = [];
                    line = fgetl(fid);
                    while ischar(line) && ~startsWith(strtrim(line), '$EndDisplacementBoundary')
                        vals = sscanf(line, '%d');
                        if numel(vals)==3
                            nodeID = vals(1);
                            startDOF = vals(2);
                            endDOF   = vals(3);

                            % 生成自由度列表
                            dofs = startDOF:endDOF;

                            % 存储
                            BC.FixedNodes = [BC.FixedNodes; nodeID];
                            BC.FixedDofs  = [BC.FixedDofs; [startDOF,endDOF]];
                            BC.Values     = [BC.Values; zeros(length(dofs),1)];  % 默认0
                        end
                        line = fgetl(fid);
                    end
                    break;
                end
            end

            fclose(fid);
        end

        % 读取节点与单元荷载信息，返回 LoadSet 对象
        function LoadData = readLoads(filename)
            % NodeLoads 行格式:
            %   nodeID startDOF endDOF v1 v2 ... vN
            % 如果只给了一个值，则认为对 startDOF:endDOF 每个自由度都相同；
            % 如果给了多个值且数量 == (endDOF-startDOF+1)，按顺序赋值到每个自由度。
            %
            % Example lines:
            %   1 1 3 1000         % node1 dof1..3 each = 1000
            %   2 1 1 -500         % node2 dof1 = -500
            %   3 1 3 100 200 300  % node3 dof1=100, dof2=200, dof3=300

            fid = fopen(filename, 'r');
            if fid < 0
                error('Cannot open file: %s', filename);
            end

            % 修正：创建 LoadSet 对象而不是空数组
            LoadData = LoadSet();

            tline = fgetl(fid);
            while ischar(tline)
                s = strtrim(tline);
                % skip comments/empty
                if isempty(s) || startsWith(s, '%')
                    tline = fgetl(fid);
                    continue;
                end

                % NodeLoads section
                if strcmpi(s, '$NodeLoads')
                    tline = fgetl(fid);
                    while ischar(tline)
                        s2 = strtrim(tline);
                        if isempty(s2) || startsWith(s2, '%')
                            tline = fgetl(fid);
                            continue;
                        end
                        if strcmpi(s2, '$EndNodeLoads')
                            break;
                        end

                        % parse numeric tokens on the line
                        vals = sscanf(s2, '%f')';
                        if numel(vals) < 4
                            warning('Skipping malformed NodeLoads line: %s', s2);
                            tline = fgetl(fid);
                            continue;
                        end
                        nodeID = int32(vals(1));
                        startDOF = int32(vals(2));
                        endDOF = int32(vals(3));
                        valueTokens = vals(4:end);

                        ndofs = double(endDOF - startDOF + 1);
                        if isempty(valueTokens)
                            warning('No value specified on NodeLoads line: %s', s2);
                            tline = fgetl(fid);
                            continue;
                        end

                        if numel(valueTokens) == 1
                            % same value for all dofs in range
                            v = valueTokens(1);
                            for d = startDOF:endDOF
                                LoadData.addNodeLoad(nodeID, d, v);
                            end
                        elseif numel(valueTokens) == ndofs
                            % per-dof values provided
                            k = 1;
                            for d = startDOF:endDOF
                                LoadData.addNodeLoad(nodeID, d, valueTokens(k));
                                k = k + 1;
                            end
                        else
                            warning('Value count does not match DOF range on line: %s', s2);
                            % Strategy: if more tokens than needed, take first ndofs; if fewer, repeat last
                            if numel(valueTokens) > ndofs
                                valueTokens = valueTokens(1:ndofs);
                            else
                                valueTokens = [valueTokens, repmat(valueTokens(end), 1, ndofs - numel(valueTokens))];
                            end
                            k = 1;
                            for d = startDOF:endDOF
                                LoadData.addNodeLoad(nodeID, d, valueTokens(k));
                                k = k + 1;
                            end
                        end

                        tline = fgetl(fid);
                        disp("荷载成功")
                    end
                end

                % ElementLoads section
%                 if strcmpi(s, '$ElementLoads')
%                     tline = fgetl(fid);
%                     while ischar(tline)
%                         s2 = strtrim(tline);
%                         if isempty(s2) || startsWith(s2, '%')
%                             tline = fgetl(fid);
%                             continue;
%                         end
%                         if strcmpi(s2, '$EndElementLoads')
%                             break;
%                         end
% 
%                         % tokenise (allow strings)
%                         tokens = strsplit(s2);
%                         if numel(tokens) < 4
%                             warning('Skipping malformed ElementLoads line: %s', s2);
%                             tline = fgetl(fid);
%                             continue;
%                         end
% 
%                         % Example semantic: entityID setID LOADTYPE params...
%                         entityID = str2double(tokens{1});
%                         setID = str2double(tokens{2});
%                         loadType = upper(tokens{3});
%                         params = tokens(4:end);
% 
%                         % 修正：使用 addSurfaceLoad 方法而不是直接赋值
%                         LoadData.addSurfaceLoad(entityID, setID, loadType, params);
% 
%                         tline = fgetl(fid);
%                     end
%                 end

                tline = fgetl(fid);
            end

            fclose(fid);

            fprintf('[IOManager] 读取 NodeLoads: %d 条 (展开为 %d 个单自由度条目)\n', ...
                length(LoadData.NodeLoads), numel(LoadData.NodeLoads));
%             fprintf('[IOManager] 读取 SurfaceLoads: %d 条\n', numel(LoadData.SurfaceLoads));
            if ~isempty(LoadData.BodyLoads)
                fprintf('[IOManager] 读取 BodyLoads: %d 条\n', numel(LoadData.BodyLoads));
            end
        end

        function writeAbaqus(filename)
            % 写入Abaqus INP格式
            % 实现INP输出逻辑
        end
    end
end